package cmd

import (
	"context"
	"fmt"
	"log/slog"
	"os"

	"gitee.com/openeuler/uos-libvirtd-exporter/collector"
	"gitee.com/openeuler/uos-libvirtd-exporter/config"
	"gitee.com/openeuler/uos-libvirtd-exporter/server"
	"gitee.com/openeuler/uos-libvirtd-exporter/signal"
	"gitee.com/openeuler/uos-libvirtd-exporter/slogx"
	"gitee.com/openeuler/uos-libvirtd-exporter/version"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

// cfgFile is the path to the configuration file
var cfgFile string

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
	Use:   "uos-libvirtd-exporter",
	Short: "A Prometheus exporter for libvirt-based virtual machines",
	Long: `UOS Libvirt Exporter is a Prometheus exporter for monitoring libvirt-based virtual machines (KVM/QEMU).
It's designed for UOS (统信操作系统) and openEuler community.

The exporter collects metrics from libvirt and exposes them in Prometheus format.`,
	Version: version.Version,
	RunE:    runExporter,
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

// init initializes the command line flags and configuration
func init() {
	// Persistent flag for configuration file
	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default: search in ./ and /etc/uos-libvirtd-exporter/)")

	// CLI flags that can override config
	rootCmd.Flags().String("libvirt.uri", "", "Libvirt connection URI")
	rootCmd.Flags().String("web.listen-address", "", "Address to listen on for web interface and telemetry")
	rootCmd.Flags().String("web.telemetry-path", "", "Path under which to expose metrics")

	// Explicitly bind environment variables
	_ = viper.BindEnv("libvirt.uri", "LIBVIRT_URI")
	_ = viper.BindEnv("web.listen_address", "WEB_LISTEN_ADDRESS")
	_ = viper.BindEnv("web.telemetry_path", "WEB_TELEMETRY_PATH")

	_ = viper.BindPFlag("libvirt.uri", rootCmd.Flags().Lookup("libvirt.uri"))
	_ = viper.BindPFlag("web.listen_address", rootCmd.Flags().Lookup("web.listen-address"))
	_ = viper.BindPFlag("web.telemetry_path", rootCmd.Flags().Lookup("web.telemetry-path"))
}

// runExporter is the main function that runs the exporter
func runExporter(cmd *cobra.Command, args []string) error {
	// Load configuration
	cfg, configFile, err := loadConfiguration()
	if err != nil {
		return err
	}

	// Initialize logger
	initializeLogger(cfg)

	// Log startup information
	logStartupInfo(cfg, configFile)

	// Initialize collector
	libvirtCollector, err := initializeCollector(cfg)
	if err != nil {
		return err
	}

	// Register collector with Prometheus
	registerCollector(libvirtCollector)

	// Setup HTTP server
	httpServer := setupHTTPServer(cfg, libvirtCollector)

	// Setup signal handling
	ctx := setupSignalHandling(libvirtCollector)

	// Start HTTP server
	return startHTTPServer(httpServer, cfg, ctx)
}

// loadConfiguration loads configuration from file or uses defaults
func loadConfiguration() (*config.Config, string, error) {
	cfg, configFile, err := config.LoadWithViper(viper.GetViper(), cfgFile)
	if err != nil {
		// Use basic logger since config failed to load
		fmt.Printf("Failed to load configuration: %v\n", err)
		return nil, "", fmt.Errorf("failed to load config: %v", err)
	}
	return cfg, configFile, nil
}

// initializeLogger creates and configures the structured logger
func initializeLogger(cfg *config.Config) {
	// Parse log level
	logLevel := slog.LevelInfo
	switch cfg.Log.Level {
	case "debug":
		logLevel = slog.LevelDebug
	case "warn", "warning":
		logLevel = slog.LevelWarn
	case "error":
		logLevel = slog.LevelError
	}

	// Parse log format
	logFormat := "text"
	if cfg.Log.Format == "json" {
		logFormat = "json"
	}

	loggerConfig := slogx.Config{
		Level:      logLevel,
		Format:     logFormat,
		FilePath:   cfg.GetLogFilePath(),
		WithStdout: cfg.Log.EnableStd,
	}

	slogx.Setup(loggerConfig)
}

// logStartupInfo logs application startup information
func logStartupInfo(cfg *config.Config, configFile string) {
	slogx.Info("Starting UOS Libvirt Exporter",
		"version", version.Version,
		"build_time", version.BuildTime,
		"product", "UOS Libvirt Exporter",
		"description", "Prometheus exporter for libvirt-based virtual machines")

	slogx.Info("Loading configuration...")

	if configFile != "" {
		slogx.Info("Configuration loaded from file", "file", configFile)
	} else {
		slogx.Info("Using default configuration")
	}

	slogx.Info("Configuration loaded successfully",
		"libvirt.uri", cfg.Libvirt.URI,
		"web.listen_address", cfg.Web.ListenAddress,
		"web.telemetry_path", cfg.Web.TelemetryPath,
		"web.expose_runtime_metrics", cfg.Web.ExposeRuntimeMetrics,
		"log.file_path", cfg.GetLogFilePath(),
		"log.level", cfg.Log.Level,
		"log.format", cfg.Log.Format)

	// 添加配置验证日志
	slogx.Debug("Configuration validation completed",
		"libvirt_uri", cfg.Libvirt.URI,
		"listen_address", cfg.Web.ListenAddress,
		"metrics_path", cfg.Web.TelemetryPath)
}

// initializeCollector creates and initializes the libvirt collector
func initializeCollector(cfg *config.Config) (*collector.LibvirtCollector, error) {
	slogx.Info("Initializing libvirt collector...")
	slogx.Debug("Establishing libvirt connection", "uri", cfg.Libvirt.URI)

	libvirtCollector, err := collector.NewLibvirtCollector(cfg.Libvirt.URI)
	if err != nil {
		slogx.Error("Failed to initialize libvirt collector", "error", err)
		return nil, fmt.Errorf("failed to create libvirt collector: %v", err)
	}

	slogx.Info("Libvirt collector initialized successfully")
	return libvirtCollector, nil
}

// registerCollector registers the collector with Prometheus
func registerCollector(libvirtCollector *collector.LibvirtCollector) {
	slogx.Info("Registering collector with Prometheus...")
	prometheus.MustRegister(libvirtCollector)
	slogx.Info("Prometheus collector registered",
		"collector_type", "libvirt",
		"collector_count", 1)
}

// setupHTTPServer creates and configures the HTTP server
func setupHTTPServer(cfg *config.Config, libvirtCollector *collector.LibvirtCollector) *server.Server {
	slogx.Debug("Creating HTTP server",
		"listen_address", cfg.Web.ListenAddress,
		"metrics_path", cfg.Web.TelemetryPath,
		"runtime_metrics_enabled", cfg.Web.ExposeRuntimeMetrics)
	return server.NewServer(cfg, libvirtCollector)
}

// setupSignalHandling sets up signal handlers for graceful shutdown
func setupSignalHandling(libvirtCollector *collector.LibvirtCollector) context.Context {
	slogx.Debug("Setting up signal handlers for graceful shutdown")
	signalHandler := signal.NewHandler(libvirtCollector)
	return signalHandler.Start()
}

// startHTTPServer starts the HTTP server and handles graceful shutdown
func startHTTPServer(httpServer *server.Server, cfg *config.Config, ctx context.Context) error {
	slogx.Info("Starting HTTP metrics server",
		"listen_address", cfg.Web.ListenAddress,
		"metrics_path", cfg.Web.TelemetryPath,
		"endpoints", []string{"/", "/health", "/ready", cfg.Web.TelemetryPath})

	if err := httpServer.Start(ctx); err != nil {
		slogx.Error("HTTP server failed", "error", err)
		return fmt.Errorf("server failed: %v", err)
	}

	slogx.Info("UOS Libvirt Exporter stopped gracefully")
	return nil
}
